/*
 * ﻿Copyright (C) 2012-2013 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.ant.taskdefs.maven;

import java.util.Properties;

import org.apache.tools.ant.taskdefs.Property;
import org.apache.tools.ant.types.LogLevel;

import com.newmainsoftech.ant.MavenProjectAccessor;
import com.newmainsoftech.ant.types.maven.MavenPath;
import com.newmainsoftech.ant.types.maven.MavenPath.MvnPathElement;

/**
 * Extension of {@link Property} <b>to conveniently get path to dependency binary in Maven local 
 * repository and define it as a property, or to add Ant property or reference to Maven property</b> 
 * especially in the usage case via maven-antrun-plugin. <br /> 
 * This can also be used in stand-alone Ant environment; in such usage, {@link #anttoo} attribute 
 * should be set true. <br />
 * <ul>
 * <li><h3><a id="usage">Usage example</a></h3>
 * Use <code>mvnproperty</code> tag in Ant's <code>target</code> tag (what can be defined in 
 * <code>configuration</code> tag of manve-antrun-plugin's <code>execution</code> tag as well 
 * as in an Ant file).
 * <ul>
 * <li><b>To get and define dependency binary's path in Maven local repository</b><br />
 * Let's say that you don't have the dependency tag defined for gwt-dev.jar in the pom.xml 
 * of your Maven project, or you may like to have the path to a dependency binary in the Maven 
 * local repository in the stand-alone Ant environment.<br />
 * <blockquote>
 * <code>&lt;<b>mvnproperty <br />
 * &nbsp;&nbsp;&nbsp;&nbsp;dependency</b>=&quot;com.google.gwt:gwt-dev:2.4.0&quot; /&gt;</code>
 * </blockquote>
 * After that, you can get the path to the gwt-dev.jar file in the Maven local repository by 
 * referring the ${com.google.gwt:gwt-dev:jar} property value as if it had been defined in 
 * the dependency section of the Maven pom.xml file.<br />
 * <br />
 * </li>
 * <li><b>To have Maven property defined at same time</b><br />
 * The usage is identical with the Ant's regular <code>property</code> tag.  
 * <blockquote>
 * <code>
 * &lt;<b>mvnproperty <br />
 * &nbsp;&nbsp;&nbsp;&nbsp;name</b>=&quot;mvnTestProp1&quot; <br />
 * &nbsp;&nbsp;&nbsp;&nbsp;<b>value</b>=&quot;mvnTestProp1Value&quot; /&gt;<br />
 * &lt;mvnproperty <br />
 * &nbsp;&nbsp;&nbsp;&nbsp;name=&quot;mvnTestProp2&quot; <br />
 * &nbsp;&nbsp;&nbsp;&nbsp;<b>location</b>=&quot;${calculated-dependency-path}&quot; /&gt;
 * </code>
 * </blockquote>
 * By those, Maven properties, mvnTestProp1 and mvnTestProp2 are defined with specified values and 
 * Ant properties as well at the same time.<br />
 * Note: Ant property is well known as immutable, however Maven property is mutable at least within 
 * a component project though it's not persistent to super project or sibling project.<br />
 * <br />
 * </li>
 *  <li>Reminder regarding Ant's ant task:<br />
 * 		When Ant's <code>&lt;ant&gt;</code> task is going to be processed, new Ant project is created as you 
 * 		may already know. Here's quote from the <a href="http://ant-contrib.sourceforge.net/tasks/tasks/antfetch_task.html"
 * 		>ant-contrib's <code>&lt;antfetch&gt;</code> task document</a>:
 * 		<blockquote>
 * 		<i>"When the <code>&lt;ant&gt;</code> task is used, in actuality, a new Ant project is created, and 
 * 		depending on the <code>inheritAll</code> property, it is populated with properties from the original 
 * 		project. Then the target in this new project is executed. <b>Any properties set in the new project 
 * 		remain with that project, they do not get "passed back" to the original project</b>. So, for example, 
 * 		if the target in the new project sets a property named "image.directory", there is no reference to 
 * 		that property in the original."</i>
 * 		</blockquote>
 * 		This is somehow important especially for the maven-antrun-plugin environment because you cannot 
 * 		use Ant's <code>&lt;import&gt;</code> task or <code>&lt;include&gt;</code> task under 
 * 		<code>configuration</code> element of the maven-antrun-plugin.<br />
 * 		Hence, <b>if, in a caller target, you like to get property value created during execution of a callee 
 * 		target what is defined in separate Ant build file, then you may use one or both of the following 
 * 		ways</b>: 
 * 		<ul>
 *  	 <li>Use <a href="http://ant-contrib.sourceforge.net/tasks/tasks/antfetch_task.html"
 * 			>ant-contrib's <code>&lt;antfetch&gt;</code> task</a>.
 *  	 </li>
 *  	 <li>In the target being called, define property as rather System property than Ant property.   
 *  	 </li>
 * 		</ul>
 * 		<br />
 * 		As one additional note, this seems to be true to reference beside property, though I have not 
 * 		further researched yet. I first thought, since it's reference, making changes on object obtained 
 * 		via the reference in callee target will be naturally reflected back in caller target. But, my 
 * 		experience with the maven-antrun-plugin does not back up my thought. Hence please be mindful for 
 * 		such situation.
 *  </li>
 * </ul>
 * </li>
 * <li><h3><a id="configuration">Configuration</a></h3>
 * <ul>
 *  <li>In the <b>case of using maven-antrun-plugin</b><br />
 *  	<ol>
 *  	 <li>Define com.newmainsoftech:ant-util:jar as a dependency of maven-antrun-plugin:
 *  		<blockquote><code>
 *  		&lt;<b>plugin</b>&gt;<br />
 *  		&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;<br />
 *  		&nbsp;&nbsp;&nbsp;&nbsp;&lt;<b>artifactId</b>&gt;<b>maven-antrun-plugin</b>&lt:/artifactId&gt;<br />
 *  		&nbsp;&nbsp;&nbsp;&nbsp;...<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;dependencies&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;<b>dependency</b>&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;<b>&lt;groupId&gt;com.newmainsoftech&lt;/groupId&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;artifactId&gt;ant-util&lt;/artifactId&gt;</b><br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;version&gt;#.#.#&lt;/version&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;/dependency&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;...<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;/dependencies&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;...<br />
 *  		&lt;/plugin&gt;<br />
 *  		</code></blockquote>
 * 		 </li>
 * 		 <li>Define <code>taskdef</code> tag under execution's configuration tag like:<br />
 * 			<blockquote><code>
 *  		&lt;<b>plugin</b>&gt;<br />
 *  		&nbsp;&nbsp;&nbsp;&nbsp;&lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;<br />
 *  		&nbsp;&nbsp;&nbsp;&nbsp;&lt;<b>artifactId</b>&gt;<b>maven-antrun-plugin</b>&lt:/artifactId&gt;<br />
 *  		&nbsp;&nbsp;&nbsp;&nbsp;...<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;executions&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;<b>execution</b>&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;id&gt;as_you_like&lt;/id&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;phase&gt;as you need&lt;/phase&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;goals&gt;&lt;goal&gt;run&lt;/goal&gt;&lt;/goals&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;<b>configuration</b>&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;target name="what_ever_you_like"&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;<b>taskdef <br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;resource="com/newmainsoftech/ant/antlib.xml" <br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;classpathref="maven.plugin.classpath"</b> /&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- Now you can use mvnpath or mvnproperty tag --&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;...<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;mvnproperty dependency="com.google.gwt:gwt-dev:2.4.0" /&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;/target&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;/configuration&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;...<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;/execution&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;
 * 			&nbsp;&nbsp;&nbsp;&nbsp;...<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;&lt;/executions&gt;<br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;...<br />
 *  		&lt;/plugin&gt;<br />
 * 			</code></blockquote>
 * 		 </li>
 * 		</ol>
 *  </li>
 *  <li>If you cannot define com.newmainsoftech:ant-util:jar as a dependency in your project's
 *  	pom.xml, or the case of stand alone Ant environment
 *  	<ol>
 *  	 <li>Define <code>path</code> tag for this project's archive ant-util.jar file and 
 *  		sub-dependencies.<br />
 *  		You need to provide each path of the following components:
 * 			<ul>
 * 			 <li>com.newmainsoftech:ant-util:jar</li>
 * 			 <li>org.sonatype.aether:aether-api:jar</li>
 * 			 <li>org.sonatype.aether:aether-util:jar</li>
 * 			 <li>org.sonatype.aether:aether-spi:jar</li>
 * 			 <li>org.sonatype.aether:aether-connector-wagon:jar</li>
 * 			 <li>org.apache.httpcomponents:httpcore:jar</li>
 * 			 <li>commons-logging:commons-logging:jar</li>
 * 			 <li>org.apache.httpcomponents:httpclient:jar</li>
 * 			 <li>org.apache.maven.wagon:wagon-http:jar</li>
 * 			 <li>org.apache.maven.wagon:wagon-http-shared4:jar</li>
 * 			</ul>
 * 			<br />
 * 			Define a <code>path</code> tag with those paths as the alternative of 
 * 			<code>maven.plugin.classpath</code> path reference of the above maven-antrun-plugin example:
 * 				<blockquote><code>
 * 				&lt;<b>path</b> <b>id</b>=&quot;<b>antutil_path</b>&quot; <br />
 * 				&nbsp;&nbsp;&nbsp;&nbsp;path=&quot;${lengthy_path_of_antutil_and_fellows}&quot; /&gt;
 * 				</code></blockquote>
 *  	 </li>
 *  	 <li>Define <code>taskdef</code> tag for the ant-util's antlib.xml<br />
 * 			<blockquote><code>
 * 			&lt;<b>taskdef</b> <br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;<b>resource=&quot;com/newmainsoftech/ant/antlib.xml&quot;</b> <br />
 * 			&nbsp;&nbsp;&nbsp;&nbsp;<b>classpathref</b>=&quot;<b>antutil_path</b>&quot; /&gt;
 * 			</code></blockquote>
 *  	 </li>
 *  	</ol>
 *  </li>
 * </ul>
 * </li>
 * </ul>
 * @author <a href="mailto:artymt@gmail.com">Arata Y.</a>
 */
public class MavenProperty extends Property {
	private MavenProjectAccessor mavenProjectAccessor = new MavenProjectAccessor();
		protected MavenProjectAccessor getMavenProjectAccessor() {
			return mavenProjectAccessor;
		}
		protected void setMavenProjectAccessor( MavenProjectAccessor mavenProjectAccessor) {
			this.mavenProjectAccessor = mavenProjectAccessor;
		}
	/**
	 * Attribute to switch whether set property as Ant property too additionally to Maven property. 
	 * Default value is true. 
	 */
	private boolean anttoo = true;
		/**
		 * Get {@link #anttoo} attribute value what is switch to controls whether set property as 
		 * Ant property too additionally to Maven property.
		 * @return value of <code>anttoo</code> attribute. 
		 */
		public synchronized boolean getAnttoo() {
			return anttoo;
		}
		/**
		 * Set value to {@link #anttoo} attribute what is switch to controls whether set property as 
		 * Ant property too additionally to Maven property.
		 * @param anttoo
		 * @see #addProperty(String, Object)
		 */
		public synchronized void setAnttoo( boolean anttoo) {
			this.anttoo = anttoo;
		}
		
	/**
	 * Add a value pair of <code>name</code> and <code>value</code> inputs as Maven property. 
	 * When {@link #anttoo} attribute value is true, the pair will be added as Ant property too as long  
	 * as another Ant property for same name does not exist since Ant property is immutable.
	 * 
	 * @param name name of property being added. 
	 * @param value value of property being added.
	 */
	@Override
	protected void addProperty( String name, Object value) {
		if ( name == null) {
			throw new IllegalArgumentException( "Value of name input cannot be null.");
		}
		
		// Obtain access to properties of MavenProject --------------------------------------------
		MavenProjectAccessor mavenProjectAccessor = getMavenProjectAccessor();
		Object mavenProjectObj = mavenProjectAccessor.getMavenProject( this);
		Properties mavenProperties = mavenProjectAccessor.getMavenProperties( mavenProjectObj);
			if ( mavenProjectObj == null) {
				if ( getAnttoo()) {
	     			log( 
	    					String.format(
	    							"Since could not detect the reference to MavenProject object, and " 
	    							+ "anttoo attribute is set to true, going to attempt to set a pair " 
	    							+ "of name (%1$s) and value (%2$s) only as an Ant property but a Maven " 
	    							+ "property.",
	    							name, value
	    							),
	    					LogLevel.INFO.getLevel()
	    					);
				}
				else {
	     			log( 
	    					String.format(
	    							"Since could not detect the reference to MavenProject object, and " 
	    							+ "anttoo attribute is set to false, returning without attempting to " 
	    							+ "set a pair of name (%1$s) and value (%2$s) at all.",
	    							name, value
	    							),
	    					LogLevel.WARN.getLevel()
	    					);
					return;
				}
			}
			else if ( mavenProperties == null) {
				throw new IllegalStateException(
						String.format(
								"Could not obtain the access to properties of Maven project (%1$s).",
								mavenProjectObj
								)
						);
			}
		// ----------------------------------------------------------------------------------------
		// Set Maven property ---------------------------------------------------------------------
		if ( mavenProperties != null) {
			String presetValue = mavenProperties.getProperty( name);
			boolean equalsFlag = false;
				if ( presetValue == value) equalsFlag = true;
					// Either preSetAntValue or value is not null
				else if (( value != null) && value.toString().equals( presetValue)) equalsFlag = true;
			
			if ( !equalsFlag) {
				mavenProperties.setProperty( name, value.toString());
				if ( presetValue == null) {
					this.log( 
							String.format(
									"Added property (key:%1$s, value:%2$s) to %3$s Maven project.",
									name, 
									value.toString(), 
									mavenProjectAccessor.getMavenProjectName( mavenProjectObj)
									), 
							LogLevel.VERBOSE.getLevel()
							);
				}
				else {
					this.log( 
							String.format(
									"Overwrited value of %1$s property from %2$s to %3$s in " 
									+ "%4$s Maven project.",
									name,
									((presetValue == null) ? "null" : presetValue),
									((value == null) ? "null" : value.toString()),
									mavenProjectAccessor.getMavenProjectName( mavenProjectObj)
									), 
							LogLevel.INFO.getLevel()
							);
				}
			}
			else {
				this.log( 
						String.format(
								"Skipped to add property (key:%1$s, value:%2$s) to %3$s Maven project " 
								+ "since it has already been defined with the same value.",
								name, 
								value.toString(), 
								mavenProjectAccessor.getMavenProjectName( mavenProjectObj)
								), 
						LogLevel.VERBOSE.getLevel()
						);
			}
		}
		// ----------------------------------------------------------------------------------------
		
		if ( getAnttoo()) {
			String preSetAntValue = getProject().getProperty( name);
			boolean equalsFlag = false;
				if ( preSetAntValue == value) equalsFlag = true;
					// Either preSetAntValue or value is not null
				else if (( value != null) && value.toString().equals( preSetAntValue)) equalsFlag = true;
				
			if ( !equalsFlag) {
	    		super.addProperty( name, value);
		        if ( preSetAntValue == null) {
		        	log( 
		        		String.format(
							"Added an Ant property of what name is %1$s and value is %2$s.",
	    					name,
	    					value),
						LogLevel.VERBOSE.getLevel()
	    				);
		        }
		        else {
		        	log( 
		        			String.format(
								"Though overwrited value of %1$s Ant property from %2$s to %3$s, " 
								+ "it will be short live since Ant property is immutable.",
								name,
								preSetAntValue,
								((value == null) ? "null" : value.toString())),
							LogLevel.INFO.getLevel()
	    					);
		        }
			}
			else {
				this.log( 
						String.format(
								"Skipped to add property (key:%1$s, value:%2$s) as an Ant property " 
								+ "since it has already been defined with the same value.",
								name, 
								value.toString() 
								), 
						LogLevel.VERBOSE.getLevel()
						);
			}
		}
	}
	
	@Override
    protected void addProperty( String name, String valueStr) {
        this.addProperty( name, (Object)valueStr);
    }
	
	public void setDependency( final String dependencyId) {
		MavenPath mavenPath = new MavenPath( getProject());
			MvnPathElement mvnPathElement = mavenPath.createMvnPathElement();
				mvnPathElement.setDependencyId( dependencyId);
				String dependencyIdCopy = mvnPathElement.getDependencyId();
				this.setName( dependencyIdCopy.substring( 0, dependencyIdCopy.lastIndexOf( ":")));
				this.setValue( mvnPathElement.getDependencyPath());
	}
}
